package sale.wxb.loveshopping.advice;

import com.github.xiaoymin.knife4j.core.util.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.security.authentication.*;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import sale.wxb.loveshopping.entity.common.Result;
import sale.wxb.loveshopping.entity.common.ResultException;
import sale.wxb.loveshopping.entity.yml.YmlConfigAuthLogin;
import sale.wxb.loveshopping.enums.ResponseEnum;

import javax.validation.ConstraintViolationException;
import java.util.List;

/**
 * 控制器层 全局异常处理 AOP方式捕获
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionController {
    @Autowired
    private YmlConfigAuthLogin ymlConfigAuthLogin;
    @Value("${spring.servlet.multipart.max-file-size}")
    private String fileMaxSize;

    /**
     * 捕获参数校验异常
     * @param ex BindException
     * @return Result
     */
    private Result<?> handleParamsError(BindException ex) {
        List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
        StringBuilder detailMsg = new StringBuilder();
        if (CollectionUtils.isNotEmpty(allErrors)) {
            for (ObjectError item : allErrors) {
                FieldError err = (FieldError) item;
                detailMsg.append(err.getField()).append(":").append(err.getDefaultMessage()).append(";");
            }
        }
        log.error(detailMsg.toString());
        return Result.fail(ResponseEnum.PARAMS, detailMsg.toString());
    }

    /**
     * ExceptionHandler的作用是用来捕获指定的异常
     * 这里捕获 Java的validation做入参的校验 的校验失败的异常
     * 统一处理
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleStoreAuthException(MethodArgumentNotValidException ex) {
        return handleParamsError(ex);
    }

    @ExceptionHandler(org.springframework.validation.BindException.class)
    public Result<?> handleBindException(org.springframework.validation.BindException ex) {
        return handleParamsError(ex);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public Result<?> handleConstraintViolationException(ConstraintViolationException ex) {
        log.error(ex.getMessage());
        return Result.fail(ResponseEnum.PARAMS, ex.getMessage());
    }

    /**
     * 捕获spring参数异常
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Result<?> handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
        log.error(ex.getMessage());
        return Result.fail(ResponseEnum.PARAMS, "参数：" + ex.getParameterName() + "存在问题，请检查。" + ex.getMessage());
    }

    @ExceptionHandler(MissingPathVariableException.class)
    public Result<?> handleMissingPathVariableException(MissingPathVariableException ex) {
        log.error(ex.getMessage());
        return Result.fail(ResponseEnum.PARAMS, "参数：" + ex.getVariableName() + "存在问题，请检查。" + ex.getMessage());
    }

    /**
     * 捕获参数异常 通常为乱填参数值，枚举值不对应等等问题
     * @param ex MethodArgumentTypeMismatchException
     * @return Result
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public Result<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
        log.error(ex.getMessage());
        return Result.fail(ResponseEnum.PARAMS, "参数："+ ex.getName() +"存在问题，请检查。");
    }

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public Result<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException ex) {
        log.error(ex.getMessage());
        return Result.fail(ResponseEnum.PARAMS, "文件大小不能超过" + fileMaxSize);
    }

    /**
     * 捕获自定义异常
     */
    @ExceptionHandler(ResultException.class)
    public Result<?> handleCustomException(ResultException ex) {
        log.error(ex.getMessage());
        return Result.fail(ex.getCode(), ex.getMessage());
    }

    /**
     * security异常 密码错误
     */
    @ExceptionHandler(BadCredentialsException.class)
    public Result<?> handleBadCredentialsException(BadCredentialsException ex) {
        log.info(ex.getMessage());
        return Result.fail(ResponseEnum.UP_FAIL, ex.getMessage());
    }

    /**
     * security 账号被禁用
     */
    @ExceptionHandler(DisabledException.class)
    public Result<?> handleDisabledException(DisabledException ex) {
        log.info(ex.getMessage());
        return Result.fail(ResponseEnum.NOT_ENABLE);
    }

    /**
     * security 账号被锁定
     */
    @ExceptionHandler(LockedException.class)
    public Result<?> handleLockedException(LockedException ex) {
        log.info(ex.getMessage());
        long h = ymlConfigAuthLogin.getLockDuration() / 60 / 60;
        String msg = String.format("连续%s次输入密码错误,您的账号已被锁定 请于%s小时后重试", ymlConfigAuthLogin.getRetryCount(), h);
        return Result.fail(ResponseEnum.ACCOUNT_LOCKED, msg);
    }

    /**
     * security 账号过期
     */
    @ExceptionHandler(AccountExpiredException.class)
    public Result<?> handleAccountExpiredException(AccountExpiredException ex) {
        log.info(ex.getMessage());
        return Result.fail(ResponseEnum.NOT_LOGIN);
    }

    /**
     * security 认证异常
     */
    @ExceptionHandler(InternalAuthenticationServiceException.class)
    public Result<?> handleInternalAuthenticationServiceException(InternalAuthenticationServiceException ex) {
        log.error(ex.getMessage());
        return Result.fail(ResponseEnum.AUTH_FAIL, ex.getMessage());
    }

    /**
     * 接口请求方式不支持异常捕获
     * @param ex HttpRequestMethodNotSupportedException
     * @return Result
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex) {
        log.error(ex.getMessage());
        return Result.fail(ResponseEnum.NOT_SUPPORTED_METHOD, "该接口不支持该请求方式：" + ex.getMethod());
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public Result<?> handleDuplicateKeyException(DuplicateKeyException ex) {
        log.error(ex.getMessage());
        return Result.fail(ResponseEnum.SQL_ERROR, "数据库操作异常：" + ex.getCause().getMessage());
    }

    /**
     * 捕获Exception异常 兜底的 上面的都没触发默认用这个
     */
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception ex) {
        ex.printStackTrace();
        return Result.fail(ResponseEnum.SERVER_ERROR, "服务端异常: " + ex);
    }
}
